﻿using System.Collections;
using System;
using static LightCAD.MathLib.MathEx;
using static LightCAD.MathLib.Constants;
using LightCAD.Three.OpenGL;
using LightCAD.MathLib;

namespace LightCAD.Three
{
    public sealed class RenderParams
    {
        public RenderCanvas canvas;
        public IGraphicsContext context = null;
        public bool depth = true;// 绘图缓存是否有一个至少6位的深度缓存(depth buffer )。 默认是true.
        public bool stencil = true;//绘图缓存是否有一个至少8位的模板缓存(stencil buffer)。默认为true
        public string precision = "highp";
        public bool alpha = false;
        public bool premultipliedAlpha = true;// renderer是否假设颜色有 premultiplied alpha. 默认为true
        public bool antialias = false;//抗锯齿
        public bool preserveDrawingBuffer = false;//是否保留缓直到手动清除或被覆盖
        public string powerPreference = "default";//提示用户代理怎样的配置更适用于当前WebGL环境。 可能是"high-performance", "low-power" 或 "default"
        public bool failIfMajorPerformanceCaveat = false;// 检测渲染器是否会因性能过差而创建失败
        public bool logarithmicDepthBuffer;//是否使用对数深度缓存。如果要在单个场景中处理巨大的比例差异，就有必要使用。
    }
    public sealed class DebugConfiguration
    {
        public bool checkShaderErrors = true;
        public Action<object, object, object> onShaderError;
    }
    public class WebGLRenderer
    {
        private readonly RenderCanvas canvas;
        private readonly IGraphicsContext context;
        private readonly bool depth;
        private readonly bool stencil;
        private bool alpha;
        private readonly bool antialias;
        private readonly bool premultipliedAlpha;
        private readonly bool preserveDrawingBuffer;
        private readonly string powerPreference;
        private readonly bool failIfMajorPerformanceCaveat;

        public WebGLRenderList currentRenderList;
        public WebGLRenderState currentRenderState;
        public readonly ListEx<WebGLRenderList> renderListStack;
        public readonly ListEx<WebGLRenderState> renderStateStack;
        public RenderCanvas domElement;
        public DebugConfiguration debug;
        public bool autoClear;
        public bool autoClearColor;
        public bool autoClearDepth;
        public bool autoClearStencil;
        public bool sortObjects;
        public ListEx<Plane> clippingPlanes;
        public bool localClippingEnabled;
        public int outputEncoding;
        public bool useLegacyLights;
        public int toneMapping;
        public double toneMappingExposure;
        private bool _isContextLost;
        private int _currentActiveCubeFace;
        private int _currentActiveMipmapLevel;
        private WebGLRenderTarget _currentRenderTarget;
        private int _currentMaterialId;
        private Camera _currentCamera = null;

        private readonly Vector4 _currentViewport;
        private readonly Vector4 _currentScissor;
        private bool _currentScissorTest;

        private int _width;
        private int _height;
        private float _pixelRatio;
        private Comparison<object> _opaqueSort;
        private Comparison<object> _transparentSort;
        private readonly Vector4 _viewport;
        private readonly Vector4 _scissor;
        private bool _scissorTest;
        private readonly Frustum _frustum;
        private bool _clippingEnabled = false;
        private bool _localClippingEnabled = false;
        private WebGLRenderTarget _transmissionRenderTarget = null;
        private Matrix4 _projScreenMatrix;
        private Vector3 _vector3;
        private Scene _emptyScene;
        private ContextAttributes contextAttributes;

        private readonly WebGLAnimation animation;
        private readonly WebGLRenderer _this;

        public WebGLRenderer(RenderCanvas canvas, RenderParams parameters = null)
        {
            canvas.MakeCurrent();
            if (parameters == null)
                parameters = new RenderParams();
            _this = this;
            this.canvas = canvas;
            context = canvas.Context;

            depth = parameters.depth;
            stencil = parameters.stencil;
            antialias = parameters.antialias;
            premultipliedAlpha = parameters.premultipliedAlpha;
            preserveDrawingBuffer = parameters.preserveDrawingBuffer;
            powerPreference = parameters.powerPreference;
            failIfMajorPerformanceCaveat = parameters.failIfMajorPerformanceCaveat;
            alpha = parameters.alpha;

            this.currentRenderList = null;
            this.currentRenderState = null;
            this.renderListStack = new ListEx<WebGLRenderList>();
            this.renderStateStack = new ListEx<WebGLRenderState>();
            this.domElement = canvas;
            this.debug = new DebugConfiguration();
            // clearing
            this.autoClear = true;
            this.autoClearColor = true;
            this.autoClearDepth = true;
            this.autoClearStencil = true;
            // scene graph

            this.sortObjects = true;

            // user-defined clipping

            this.clippingPlanes = new ListEx<Plane>();
            this.localClippingEnabled = false;

            // physically based shading
            this.outputEncoding = LinearEncoding;
            // physical lights

            this.useLegacyLights = true;

            // tone mapping

            this.toneMapping = NoToneMapping;
            this.toneMappingExposure = 1.0;
            this._isContextLost = false;

            // internal state cache

            this._currentActiveCubeFace = 0;
            this._currentActiveMipmapLevel = 0;
            this._currentRenderTarget = null;
            this._currentMaterialId = -1;

            this._currentCamera = null;

            this._currentViewport = new Vector4();
            this._currentScissor = new Vector4();
            this._currentScissorTest = false;

            this._width = this.canvas.Width;
            this._height = this.canvas.Height;
            this._pixelRatio = 1;
            this._opaqueSort = null;
            this._transparentSort = null;

            this._viewport = new Vector4(0, 0, _width, _height);
            this._scissor = new Vector4(0, 0, _width, _height);
            this._scissorTest = false;

            // frustum
            this._frustum = new Frustum();

            // clipping

            this._clippingEnabled = false;
            this._localClippingEnabled = false;

            // transmission

            this._transmissionRenderTarget = null;

            // camera matrices cache

            this._projScreenMatrix = new Matrix4();

            this._vector3 = new Vector3();
            this._emptyScene = new Scene { background = null, fog = null, environment = null, overrideMaterial = null };

            try
            {

                this.contextAttributes = new ContextAttributes
                {
                    alpha = true,
                    depth = depth,
                    stencil = stencil,
                    antialias = antialias,
                    premultipliedAlpha = premultipliedAlpha,
                    preserveDrawingBuffer = preserveDrawingBuffer,
                    powerPreference = powerPreference,
                    failIfMajorPerformanceCaveat = failIfMajorPerformanceCaveat
                };
                // Some experimental-webgl implementations do not have getShaderPrecisionFormat

            }
            catch (Exception error)
            {

                console.error("THREE.WebGLRenderer: " + error.Message);
                throw error;
            }
            this.animation = new WebGLAnimation();
            animation.setAnimationLoop(onAnimationFrame);
            //不知道怎么改写
            //if (typeof(self) != "undefined")
            animation.setContext(canvas.Context);
            //xr.addEventListener("sessionstart", onXRSessionStart);
            //xr.addEventListener("sessionend", onXRSessionEnd);
            initGLContext(parameters);

     
        }
        public void initGLContext(RenderParams parameters)
        {

            extensions = new WebGLExtensions();

            capabilities = new WebGLCapabilities(extensions, parameters);

            extensions.init(capabilities);

            utils = new WebGLUtils(extensions, capabilities);

            state = new WebGLState(extensions, capabilities);

            info = new WebGLInfo();
            properties = new WebGLProperties();
            textures = new WebGLTextures(extensions, state, properties, capabilities, utils, info);
            cubemaps = new WebGLCubeMaps(_this);
            cubeuvmaps = new WebGLCubeUVMaps(_this);
            attributes = new WebGLAttributes(capabilities);
            bindingStates = new WebGLBindingStates(extensions, attributes, capabilities);
            geometries = new WebGLGeometries(attributes, info, bindingStates);
            objects = new WebGLObjects(geometries, attributes, info);
            morphtargets = new WebGLMorphtargets(capabilities, textures);
            clipping = new WebGLClipping(properties);
            programCache = new WebGLPrograms(_this, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping);
            materials = new WebGLMaterials(_this, properties);
            renderLists = new WebGLRenderLists();
            renderStates = new WebGLRenderStates(extensions, capabilities);
            background = new WebGLBackground(_this, cubemaps, cubeuvmaps, state, objects, alpha, premultipliedAlpha);
            shadowMap = new WebGLShadowMap(_this, objects, capabilities);
            uniformsGroups = new WebGLUniformsGroups(info, capabilities, state);

            bufferRenderer = new WebGLBufferRenderer(extensions, info, capabilities);
            indexedBufferRenderer = new WebGLIndexedBufferRenderer(extensions, info, capabilities);
            indexedBufferIndirectRenderer = new WebGLIndexedBufferIndirectRenderer(extensions, info, capabilities);
            info.programs = programCache.programs;

            _this.capabilities = capabilities;
            _this.extensions = extensions;
            _this.properties = properties;
            _this.renderLists = renderLists;
            _this.shadowMap = shadowMap;
            _this.state = state;
            _this.info = info;

        }

        public float getTargetPixelRatio()
        {

            return _currentRenderTarget == null ? _pixelRatio : 1;

        }
        public IGraphicsContext getContext()//contextNames, contextAttributes )
        {
            return canvas.Context;
        }
        public sealed class ContextAttributes
        {
            public bool alpha;
            public bool depth;
            public bool stencil;
            public bool antialias;
            public bool premultipliedAlpha;
            public bool preserveDrawingBuffer;
            public string powerPreference;
            public bool failIfMajorPerformanceCaveat;
        }

        public WebGLExtensions extensions;
        public WebGLCapabilities capabilities;
        public WebGLState state;
        public WebGLInfo info;
        public WebGLProperties properties;
        public WebGLTextures textures;
        public WebGLCubeMaps cubemaps;
        public WebGLCubeUVMaps cubeuvmaps;
        public WebGLAttributes attributes;
        public WebGLGeometries geometries;
        public WebGLObjects objects;
        public WebGLPrograms programCache;
        public WebGLMaterials materials;
        public WebGLRenderLists renderLists;
        public WebGLRenderStates renderStates;
        public WebGLClipping clipping;
        public WebGLShadowMap shadowMap;
        public WebGLBackground background;
        public WebGLMorphtargets morphtargets;
        public WebGLBufferRenderer bufferRenderer;
        public WebGLIndexedBufferRenderer indexedBufferRenderer;
        public WebGLIndexedBufferIndirectRenderer indexedBufferIndirectRenderer;

        public WebGLUtils utils;
        public WebGLBindingStates bindingStates;
        public WebGLUniformsGroups uniformsGroups;
        // API

        //this.getContext = function()
        //           {

        //               return gl;

        //           };

        //this.getContextAttributes = function()
        //           {

        //               return gl.getContextAttributes();

        //           };

        //this.forceContextLoss = function()
        //           {

        //               const extension = extensions.get("WEBGL_lose_context");
        //               if (extension) extension.loseContext();

        //           };

        //this.forceContextRestore = function()
        //           {

        //               const extension = extensions.get("WEBGL_lose_context");
        //               if (extension) extension.restoreContext();

        //           };
        public double getPixelRatio()
        {
            return _pixelRatio;
        }

        public void setPixelRatio(float value)
        {
            _pixelRatio = value;
            this.setSize(_width, _height, false);
        }
        public void setPixelRatio(double value)
        {
            setPixelRatio((float)value);
        }
        public Vector2 getSize(Vector2 target = null)
        {
            target = target ?? new Vector2();
            return target.Set(_width, _height);

        }

        public void setSize(double width, double height, bool updateStyle = true)
        {

            _width = (int)width;
            _height = (int)height;

            canvas.Width = (int)Math.Floor(width * _pixelRatio);
            canvas.Height = (int)Math.Floor(height * _pixelRatio);

            if (updateStyle)
            {

                //_canvas.style.width = width + "px";
                //_canvas.style.height = height + "px";

            }

            this.setViewport(0, 0, width, height);

        }
        public Vector2 getDrawingBufferSize(Vector2 target)
        {

            return target.Set(_width * _pixelRatio, _height * _pixelRatio).Floor();

        }

        public void setDrawingBufferSize(int width, int height, float pixelRatio)
        {

            _width = width;
            _height = height;

            _pixelRatio = pixelRatio;

            //_canvas.Width = (int)Math.Floor(width * pixelRatio);
            //_canvas.Height = (int)Math.Floor(height * pixelRatio);

            this.setViewport(0, 0, width, height);

        }

        public Vector4 getCurrentViewport(Vector4 target)
        {

            return target.Copy(_currentViewport);

        }

        public Vector4 getViewport(Vector4 target)
        {

            return target.Copy(_viewport);

        }

        public void setViewPort(Vector4 size)
        {
            setViewport(size.X, size.Y, size.Z, size.W);
        }
        public void setViewport(double x, double y, double width, double height)
        {
            _viewport.Set(x, y, width, height);
            //_viewport.set(x, _height - y - height, width, height);
            state.viewport(_currentViewport.Copy(_viewport).MultiplyScalar(_pixelRatio).Floor());

        }
        public Vector4 getScissor(Vector4 target)
        {
            return target.Copy(_scissor);
        }
        public void setScissor(Vector4 scissor)
        {
            setScissor((int)scissor.X, (int)scissor.Y, (int)scissor.Z, (int)scissor.W);
        }
        public void setScissor(int x, int y, int width, int height)
        {
            _scissor.Set(x, y, width, height);
            state.scissor(_currentScissor.Copy(_scissor).MultiplyScalar(_pixelRatio).Floor());
        }

        public bool getScissorTest()
        {

            return _scissorTest;

        }

        public void setScissorTest(bool scissorTest)
        {
            state.setScissorTest(_scissorTest = scissorTest);
        }
        public void setOpaqueSort(Comparison<object> method)
        {
            _opaqueSort = method;
        }

        public void setTransparentSort(Comparison<object> method)
        {
            _transparentSort = method;
        }

        // Clearing

        public Color getClearColor(Color target)
        {

            return target.Copy(background.getClearColor());

        }
        public void setClearColor(uint color, float alpha = 1)
        {
            background.setClearColor(new Color(color), alpha);
        }
        public void setClearColor(object color, float alpha = 1)
        {
            background.setClearColor(color, alpha);
        }

        public float getClearAlpha()
        {

            return background.getClearAlpha();

        }

        public void setClearAlpha(float alpha)
        {
            background.setClearAlpha(alpha);
        }

        public void clear(bool color = true, bool depth = true, bool stencil = true)
        {
            var bits = 0;

            if (color) bits |= gl.COLOR_BUFFER_BIT;
            if (depth) bits |= gl.DEPTH_BUFFER_BIT;
            if (stencil) bits |= gl.STENCIL_BUFFER_BIT;

            gl.clear(bits);

        }

        public void clearColor()
        {

            this.clear(true, false, false);

        }

        public void clearDepth()
        {

            this.clear(false, true, false);

        }

        public void clearStencil()
        {

            this.clear(false, false, true);

        }
        public void dispose()
        {
            //_canvas.removeEventListener("webglcontextlost", onContextLost, false);
            //_canvas.removeEventListener("webglcontextrestored", onContextRestore, false);
            //_canvas.removeEventListener("webglcontextcreationerror", onContextCreationError, false);

            renderLists.dispose();
            renderStates.dispose();
            properties.dispose();
            cubemaps.dispose();
            cubeuvmaps.dispose();
            objects.dispose();
            bindingStates.dispose();
            uniformsGroups.dispose();
            programCache.dispose();


            if (_transmissionRenderTarget != null)
            {

                _transmissionRenderTarget.dispose();
                _transmissionRenderTarget = null;

            }

            animation.stop();

        }
        #region Event
        // Events

        //           function onContextLost( event ) {


        //       event.preventDefault();

        //	console.log( "THREE.WebGLRenderer: Context Lost." );

        //	_isContextLost = true;

        //}

        //       function onContextRestore( /* event */ )
        //       {

        //           console.log("THREE.WebGLRenderer: Context Restored.");

        //           _isContextLost = false;

        //           const infoAutoReset = info.autoReset;
        //           const shadowMapEnabled = shadowMap.enabled;
        //           const shadowMapAutoUpdate = shadowMap.autoUpdate;
        //           const shadowMapNeedsUpdate = shadowMap.needsUpdate;
        //           const shadowMapType = shadowMap.type;

        //           initGLContext();

        //           info.autoReset = infoAutoReset;
        //           shadowMap.enabled = shadowMapEnabled;
        //           shadowMap.autoUpdate = shadowMapAutoUpdate;
        //           shadowMap.needsUpdate = shadowMapNeedsUpdate;
        //           shadowMap.type = shadowMapType;

        //       }

        //       function onContextCreationError( event ) {

        //           console.error("THREE.WebGLRenderer: A WebGL context could not be created. Reason: ", event.statusMessage );

        //       }
        public void onMaterialDispose(EventArgs e)
        {

            var material = (Material)e.target;

            material.removeEventListener("dispose", onMaterialDispose);

            deallocateMaterial(material);

        }
        // Buffer deallocation

        public void deallocateMaterial(Material material)
        {

            releaseMaterialProgramReferences(material);

            properties.remove(material);

        }


        public void releaseMaterialProgramReferences(Material material)
        {

            var programs = properties.get(material).programs;

            if (programs != null)
            {
                var programsArr = programs as JsObj<WebGLProgram>;

                foreach (var kv in programsArr)
                {
                    var program = kv.Value;
                    programCache.releaseProgram(program);
                }
                if (material is ShaderMaterial)
                {
                    programCache.releaseShaderCache(material as ShaderMaterial);
                }

            }

        }


        #endregion

        // Buffer rendering

        public void renderBufferDirect(Camera camera, Object3D scene, BufferGeometry geometry, Material material, Object3D _object, GeometryGroup group)
        {

            if (scene == null) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null)

            var frontFaceCW = (_object is Mesh && _object.matrixWorld.Determinant() < 0);

            var program = setProgram(camera, scene, geometry, material, _object);

            state.setMaterial(material, frontFaceCW);

            //

            var index = geometry.index;
            var rangeFactor = 1;

            if (material.wireframe)
            {

                index = geometries.getWireframeAttribute(geometry);
                rangeFactor = 2;

            }

            //
            var drawRange = geometry.drawRange;
            var position = geometry.attributes.position;

            var drawStart = drawRange.start * rangeFactor;
            var drawEnd = MathEx.Multiply(MathEx.Add(drawRange.start, drawRange.count), rangeFactor);

            if (group != null)
            {

                drawStart = Math.Max(drawStart, MathEx.Multiply(group.Start, rangeFactor));
                drawEnd = Math.Min(drawEnd, MathEx.Multiply(MathEx.Add(group.Start, group.Count), rangeFactor));

            }

            {
                if (index != null)
                {

                    drawStart = Math.Max(drawStart, 0);
                    drawEnd = Math.Min(drawEnd, index.count);

                }
                else if (position != null && position != null)
                {

                    drawStart = Math.Max(drawStart, 0);
                    drawEnd = Math.Min(drawEnd, position.count);

                }
            }


            var drawCount = drawEnd - drawStart;

            if (drawCount < 0 || drawCount == Infinity) return;

            //

            bindingStates.setup(_object, material, program, geometry, index);

            BufferObject attribute;
            WebGLBufferRenderer renderer = bufferRenderer;

            if (index != null)
            {

                attribute = attributes.get(index);
                //if (_object is IMultiIndirectObject)
                //{
                //    renderer = indexedBufferIndirectRenderer;
                //    indexedBufferIndirectRenderer.setIndex(attribute);
                //    indexedBufferIndirectRenderer.setCmds((_object as IMultiIndirectObject), group);
                //}
                //else
                {
                    renderer = indexedBufferRenderer;
                    indexedBufferRenderer.setIndex(attribute);
                }
            }

            //

            if (_object is Mesh)
            {

                if (material.wireframe)
                {

                    state.setLineWidth((float)(material.wireframeLinewidth * getTargetPixelRatio()));
                    renderer.setMode(gl.LINES);

                }
                else
                {

                    renderer.setMode(gl.TRIANGLES);

                }

            }
            else if (_object is Line)
            {

                var lineWidth = (material as LineBasicMaterial).linewidth;

                if (lineWidth <= 0) lineWidth = 1; // Not using Line*Material

                state.setLineWidth((float)(lineWidth * getTargetPixelRatio()));

                if (_object is LineSegments)
                {

                    renderer.setMode(gl.LINES);

                }
                else if (_object is LineLoop)
                {

                    renderer.setMode(gl.LINE_LOOP);

                }
                else
                {

                    renderer.setMode(gl.LINE_STRIP);

                }

            }
            else if (_object is Points)
            {

                renderer.setMode(gl.POINTS);

            }
            else if (_object is Sprite)
            {

                renderer.setMode(gl.TRIANGLES);

            }

            if (_object is InstancedMesh)
            {

                renderer.renderInstances(drawStart, drawCount, (_object as InstancedMesh).count);

            }
            else if (geometry is InstancedBufferGeometry)
            {

                var instanceGeo = geometry as InstancedBufferGeometry;
                var maxInstanceCount = instanceGeo._maxInstanceCount != Infinity ? instanceGeo._maxInstanceCount : Infinity;
                var instanceCount = Math.Min(instanceGeo.instanceCount, maxInstanceCount);

                renderer.renderInstances(drawStart, drawCount, instanceCount);

            }
            else
            {

                renderer.render(drawStart, drawCount);

            }

        }

        // Compile

        public void compile(Object3D scene, Camera camera)
        {

            void prepare(Material material, Object3D _scene, Object3D _object)
            {
                if (material.transparent && material.side == DoubleSide && material.forceSinglePass == false)
                {

                    material.side = BackSide;
                    material.needsUpdate = true;
                    getProgram(material, _scene, _object);

                    material.side = FrontSide;
                    material.needsUpdate = true;
                    getProgram(material, _scene, _object);

                    material.side = DoubleSide;

                }
                else
                {
                    getProgram(material, _scene, _object);
                }
            }

            currentRenderState = renderStates.get(scene);
            currentRenderState.init();

            renderStateStack.Push(currentRenderState);

            scene.traverseVisible((_object) =>
            {

                if (_object is Light && _object.layers.test(camera.layers))
                {
                    var light = _object as Light;
                    currentRenderState.pushLight(light);
                    if (_object.castShadow)
                    {
                        currentRenderState.pushShadow(light);
                    }
                }
            });

            currentRenderState.setupLights(_this.useLegacyLights);

            scene.traverse((_object) =>
            {
                if (_object is IMaterialObject)
                {
                    var materials = (_object as IMaterialObject).getMaterials();
                    for (var i = 0; i < materials.Length; i++)
                    {

                        var material2 = materials[i];
                        prepare(material2, scene, _object);
                    }
                }
                //else if (_object is IMaterialObject)
                //{
                //    var material = (_object as IMaterialObject).getMaterial();
                //    prepare(material, scene, _object);
                //}
            });

            renderStateStack.Pop();
            currentRenderState = null;
        }
        // Animation Loop

        private Action<double, object> onAnimationFrameCallbackAction = null;

        public void onAnimationFrame(double time, object Frame)
        {
            //var xrFrame = Frame as XRFrame;
            //onAnimationFrameCallbackAction?.Invoke(time, xrFrame);

        }

        public void onXRSessionStart(EventArgs e)
        {

            animation.stop();

        }

        public void onXRSessionEnd(EventArgs e)
        {

            animation.start();

        }

        public void setAnimationLoop(Action<double, object> callback)
        {

            onAnimationFrameCallbackAction = callback;
            //xr.setAnimationLoop(callback);

            if (callback == null)
                animation.stop();
            else
                animation.start();

        }


        // Rendering

        public void render(Object3D scene, Camera camera)
        {
            console.time("render");

            //if (camera != undefined && camera.isCamera != true)
            //{

            //    console.error("THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.");
            //    return;

            //}

            if (_isContextLost) return;

            // update scene graph

            if (scene.matrixWorldAutoUpdate) scene.updateMatrixWorld();

            // update camera matrices and frustum

            if (camera.parent == null && camera.matrixWorldAutoUpdate) camera.updateMatrixWorld();

            //if (xr.enabled  && xr.isPresenting )
            //{

            //    if (xr.cameraAutoUpdate ) xr.updateCamera(camera);

            //    camera = xr.getCamera(); // use XR camera for rendering

            //}

            //
            if (scene is Scene) (scene as Scene)?.onBeforeRender(_this, scene, camera, _currentRenderTarget, null, null);

            currentRenderState = renderStates.get(scene, renderStateStack.Length);
            currentRenderState.init();

            renderStateStack.Push(currentRenderState);

            _projScreenMatrix.MultiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
            _frustum.SetFromProjectionMatrix(_projScreenMatrix);

            _localClippingEnabled = this.localClippingEnabled;
            _clippingEnabled = clipping.init(this.clippingPlanes, _localClippingEnabled);

            currentRenderList = renderLists.get(scene, renderListStack.Length);
            currentRenderList.init();

            renderListStack.Push(currentRenderList);
            console.time("render", "before project");

            projectObject(scene, camera, 0, _this.sortObjects);
            console.time("render", "projectObject");
            currentRenderList.finish();

            if (_this.sortObjects)
            {

                currentRenderList.sort(_opaqueSort, _transparentSort);

            }
            console.time("render", "sortObjects");

            //

            if (_clippingEnabled) clipping.beginShadows();

            var shadowsArray = currentRenderState.state.shadowsArray;

            shadowMap.render(shadowsArray, scene, camera);

            if (_clippingEnabled) clipping.endShadows();

            //

            if (this.info.autoReset) this.info.reset();

            //
            background.render(currentRenderList, scene);

            // render scene

            currentRenderState.setupLights(_this.useLegacyLights);


            console.time("render", "before renderScene");

            if (camera is ArrayCamera)
            {
                var arrCamera = camera as ArrayCamera;
                var cameras = arrCamera.cameras;

                for (int i = 0, l = cameras.Length; i < l; i++)
                {

                    var camera2 = cameras[i];

                    renderScene(currentRenderList, scene, camera2, camera2.viewport);

                }

            }
            else
            {

                renderScene(currentRenderList, scene, camera);

            }

            console.time("render", "renderScene");
            //

            if (_currentRenderTarget != null)
            {

                // resolve multisample renderbuffers to a single-sample texture if necessary
                //如有必要，将多采样渲染缓冲区解析为单个采样纹理
                textures.updateMultisampleRenderTarget(_currentRenderTarget);

                // Generate mipmap if we"re using any kind of mipmap filtering
                //如果我们使用任何类型的mipmap过滤，则生成mipmap
                textures.updateRenderTargetMipmap(_currentRenderTarget);

            }

            //

            if (scene is Scene) scene.onAfterRender(_this, scene, camera, null, null, null);

            // gl.finish();

            bindingStates.resetDefaultState();
            _currentMaterialId = -1;
            _currentCamera = null;

            renderStateStack.Pop();

            if (renderStateStack.Length > 0)
            {

                currentRenderState = renderStateStack[renderStateStack.Length - 1];

            }
            else
            {

                currentRenderState = null;

            }

            renderListStack.Pop();

            if (renderListStack.Length > 0)
            {

                currentRenderList = renderListStack[renderListStack.Length - 1];

            }
            else
            {

                currentRenderList = null;

            }

            console.time("render", "renderEnd");


        }
        public void projectObject(Object3D _object, Camera camera, int groupOrder, bool sortObjects)
        {
            if (_object == null)
            {
                return;
            }
            if (_object.visible == false || _object.isMerged) return;//MergeGroup的子节点不参与渲染

            var visible = _object.layers.test(camera.layers);

            if (visible)
            {

                if (_object is Group)
                {

                    groupOrder = _object.renderOrder;

                }
                else if (_object is LOD)
                {
                    var lod = _object as LOD;
                    if (lod.autoUpdate) lod.update(camera);

                }
                else if (_object is Light)
                {
                    var light = _object as Light;
                    currentRenderState.pushLight(light);

                    if (light.castShadow)
                    {

                        currentRenderState.pushShadow(light);

                    }

                }
                else if (_object is Sprite)
                {
                    var sprite = _object as Sprite;
                    if (!sprite.frustumCulled || _frustum.IntersectsSprite(sprite))
                    {

                        if (sortObjects)
                        {

                            _vector3.SetFromMatrixPosition(sprite.matrixWorld)
                                .ApplyMatrix4(_projScreenMatrix);

                        }

                        var geometry = objects.update(sprite);
                        var material = sprite.material;

                        if (material.visible)
                        {

                            currentRenderList.push(sprite, geometry, material, groupOrder, _vector3.Z, null);

                        }

                    }

                }
                else if (_object is Mesh || _object is Line || _object is Points)
                {

                    if (_object is SkinnedMesh)
                    {

                        // update skeleton only once in a frame
                        var skindedMesh = _object as SkinnedMesh;
                        if (skindedMesh.skeleton.frame != info.render.frame)
                        {

                            skindedMesh.skeleton.update();
                            skindedMesh.skeleton.frame = info.render.frame;

                        }

                    }

                    if (_object.frustumCulled || _frustum.IntersectsObject(_object))
                    {
                        if (sortObjects)
                        {
                            _vector3.SetFromMatrixPosition(_object.matrixWorld)
                                .ApplyMatrix4(_projScreenMatrix);
                        }

                        var geometry = objects.update(_object);
                        if (_object is IMaterialObject && ((_object as IMaterialObject).isMultiMaterial()|| geometry.groups.Length==1))
                        {
                            var material = (_object as IMaterialObject).getMaterials();

                            var groups = geometry.groups;

                            for (int i = 0, l = groups.Length; i < l; i++)
                            {

                                var group = groups[i];
                                var groupMaterial = material[group.MaterialIndex];

                                if (groupMaterial != null && groupMaterial.visible)
                                {

                                    currentRenderList.push(_object, geometry, groupMaterial, groupOrder, _vector3.Z, group);

                                }

                            }
                        }
                        else if (_object is IMaterialObject)
                        {

                            var material = (_object as IMaterialObject).getMaterial();
                            if (material.visible)
                                currentRenderList.push(_object, geometry, material, groupOrder, _vector3.Z, null);

                        }

                    }

                }

            }

            var children = _object.children;

            for (int i = 0, l = children.Length; i < l; i++)
            {

                projectObject(children[i], camera, groupOrder, sortObjects);

            }
            //if (_object is MergeGroup)
            //{
            //    var mgrp = _object as MergeGroup;
            //    if (mgrp.merged)
            //    {
            //        mgrp.upateChildrenDeferChanges();
            //        foreach (var item in mgrp.mergeObjInfoMap)
            //        {
            //            projectObject(item.Key as Object3D, camera, groupOrder, sortObjects);
            //        }
            //    }
            //}
        }

        public void renderScene(WebGLRenderList currentRenderList, Object3D scene, Camera camera, Vector4 viewport = null)
        {

            var opaqueObjects = currentRenderList.opaque;
            var transmissiveObjects = currentRenderList.transmissive;
            var transparentObjects = currentRenderList.transparent;

            currentRenderState.setupLightsView(camera);

            if (_clippingEnabled) clipping.setGlobalState(_this.clippingPlanes, camera);

            if (transmissiveObjects.Length > 0) renderTransmissionPass(opaqueObjects, transmissiveObjects, scene, camera);

            if (viewport != null) state.viewport(_currentViewport.Copy(viewport));

            if (opaqueObjects.Length > 0) renderObjects(opaqueObjects, scene, camera);
            if (transmissiveObjects.Length > 0) renderObjects(transmissiveObjects, scene, camera);
            if (transparentObjects.Length > 0) renderObjects(transparentObjects, scene, camera);

            // Ensure depth buffer writing is enabled so it can be cleared on next render

            state.buffers.depth.setTest(true);
            state.buffers.depth.setMask(true);
            state.buffers.color.setMask(true);

            state.setPolygonOffset(false);

        }

        public void renderTransmissionPass(ListEx<RenderItem> opaqueObjects, ListEx<RenderItem> transmissiveObjects, Object3D scene, Camera camera)
        {

            if (_transmissionRenderTarget == null)
            {
                var isWebGL2 = capabilities.isWebGL2;
                _transmissionRenderTarget = new WebGLRenderTarget(1024, 1024, new RenderTargetOptions
                {
                    generateMipmaps = true,
                    type = true || extensions.has("EXT_color_buffer_half_float") ? HalfFloatType : UnsignedByteType,//opengl默认支持
                    minFilter = LinearMipmapLinearFilter,
                    samples = (isWebGL2 && antialias) ? 4 : 0
                });
                // debug

                /*
                const geometry = new PlaneGeometry();
                const material = new MeshBasicMaterial( { map: _transmissionRenderTarget.texture } );

                const mesh = new Mesh( geometry, material );
                scene.add( mesh );
                */

            }

            //

            var currentRenderTarget = _this.getRenderTarget();
            _this.setRenderTarget(_transmissionRenderTarget);
            _this.clear();

            // Turn off the features which can affect the frag color for opaque objects pass.
            // Otherwise they are applied twice in opaque objects pass and transmission objects pass.
            var currentToneMapping = _this.toneMapping;
            _this.toneMapping = NoToneMapping;

            renderObjects(opaqueObjects, scene, camera);

            textures.updateMultisampleRenderTarget(_transmissionRenderTarget);
            textures.updateRenderTargetMipmap(_transmissionRenderTarget);

            var renderTargetNeedsUpdate = false;

            for (int i = 0, l = transmissiveObjects.Length; i < l; i++)
            {

                var renderItem = transmissiveObjects[i];

                var _object = renderItem._object;
                var geometry = renderItem.geometry;
                var material = renderItem.material;
                var group = renderItem.group;

                if (material.side == DoubleSide && _object.layers.test(camera.layers))
                {
                    var currentSide = material.side;

                    material.side = BackSide;
                    material.needsUpdate = true;

                    renderObject(_object, scene, camera, geometry, material, group);

                    material.side = currentSide;
                    material.needsUpdate = true;

                    renderTargetNeedsUpdate = true;
                }

            }

            if (renderTargetNeedsUpdate)
            {

                textures.updateMultisampleRenderTarget(_transmissionRenderTarget);
                textures.updateRenderTargetMipmap(_transmissionRenderTarget);

            }

            _this.setRenderTarget(currentRenderTarget);
            this.toneMapping = currentToneMapping;
        }

        public void renderObjects(ListEx<RenderItem> renderList, Object3D scene, Camera camera)
        {

            var overrideMaterial = scene is Scene ? (scene as Scene).overrideMaterial : null;

            for (int i = 0, l = renderList.Length; i < l; i++)
            {

                var renderItem = renderList[i];

                var _object = renderItem._object;
                var geometry = renderItem.geometry;
                var material = overrideMaterial == null ? renderItem.material : overrideMaterial;
                var group = renderItem.group;

                if (_object.layers.test(camera.layers))
                {

                    renderObject(_object, scene, camera, geometry, material, group);

                }

            }

        }

        public void renderObject(Object3D _object, Object3D scene, Camera camera, BufferGeometry geometry, Material material, GeometryGroup group)
        {

            _object.onBeforeRender(_this, scene, camera, geometry, material, group);

            _object.modelViewMatrix.MultiplyMatrices(camera.matrixWorldInverse, _object.matrixWorld);
            _object.normalMatrix.GetNormalMatrix(_object.modelViewMatrix);

            material.onBeforeRender(_this, scene, camera, geometry, _object, group);

            if (material.transparent && material.side == DoubleSide && material.forceSinglePass == false)
            {

                material.side = BackSide;
                material.needsUpdate = true;
                _this.renderBufferDirect(camera, scene, geometry, material, _object, group);

                material.side = FrontSide;
                material.needsUpdate = true;
                _this.renderBufferDirect(camera, scene, geometry, material, _object, group);

                material.side = DoubleSide;

            }
            else
            {

                _this.renderBufferDirect(camera, scene, geometry, material, _object, group);

            }

            _object.onAfterRender(_this, scene, camera, geometry, material, group);

        }

        public WebGLProgram getProgram(Material material, Object3D scene, Object3D _object)
        {
            if (scene is Scene != true) scene = _emptyScene; // scene could be a Mesh, Line, Points, ...
            var sceneObj = scene as Scene;
            var materialProperties = properties.get(material);

            var lights = currentRenderState.state.lights;
            var shadowsArray = currentRenderState.state.shadowsArray;

            var lightsStateVersion = lights.state.version;

            var parameters = programCache.getParameters(material, lights.state, shadowsArray, sceneObj, _object as IGeometry);
            var programCacheKey = programCache.getProgramCacheKey(parameters);
            var programsObj = materialProperties.programs;
            JsObj<WebGLProgram> programs = programsObj != null ? programsObj as JsObj<WebGLProgram> : null;

            // always update environment and fog - changing these trigger an getProgram call, but it"s possible that the program doesn"t change

            materialProperties.environment = material is MeshStandardMaterial ? sceneObj.environment : null;
            materialProperties.fog = sceneObj.fog;
            materialProperties.envMap = (material is MeshStandardMaterial ? cubeuvmaps : cubemaps).get(material.envMap ?? (Texture)materialProperties.environment);

            if (programs == null)
            {

                // new material

                material.addEventListener("dispose", onMaterialDispose);

                programs = new JsObj<WebGLProgram>();
                materialProperties.programs = programs;

            }

            WebGLProgram program = programs.get(programCacheKey);

            if (program != null)
            {

                // early out if program and light state is identical

                if (materialProperties.currentProgram == program && materialProperties.lightsStateVersion == lightsStateVersion)
                {

                    updateCommonMaterialProperties(material, parameters);

                    return program;

                }

            }
            else
            {

                parameters.uniforms = programCache.getUniforms(material);

                material.onBuild(_object, parameters, _this);

                material.onBeforeCompile(parameters, _this);

                program = programCache.acquireProgram(parameters, programCacheKey);
                programs.set(programCacheKey, program);

                materialProperties.uniforms = parameters.uniforms;

            }

            var uniforms = materialProperties.uniforms;

            if ((!(material is ShaderMaterial) && !(material is RawShaderMaterial)) || material.clipping)
            {
                uniforms["clippingPlanes"] = clipping.uniform;

            }

            updateCommonMaterialProperties(material, parameters);

            // store the light setup it was created for

            materialProperties.needsLights = materialNeedsLights(material);
            materialProperties.lightsStateVersion = lightsStateVersion;

            if (materialProperties.needsLights)
            {
                // wire up the material to this renderer"s lighting state

                uniforms.ambientLightColor.value = lights.state.ambient;
                uniforms.lightProbe.value = lights.state.probe;
                uniforms.directionalLights.value = lights.state.directional;
                uniforms.directionalLightShadows.value = lights.state.directionalShadow;
                uniforms.spotLights.value = lights.state.spot;
                uniforms.spotLightShadows.value = lights.state.spotShadow;
                uniforms.rectAreaLights.value = lights.state.rectArea;
                uniforms.ltc_1.value = lights.state.rectAreaLTC1;
                uniforms.ltc_2.value = lights.state.rectAreaLTC2;
                uniforms.pointLights.value = lights.state.point;
                uniforms.pointLightShadows.value = lights.state.pointShadow;
                uniforms.hemisphereLights.value = lights.state.hemi;

                uniforms.directionalShadowMap.value = lights.state.directionalShadowMap;
                uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix;
                uniforms.spotShadowMap.value = lights.state.spotShadowMap;
                uniforms.spotLightMatrix.value = lights.state.spotLightMatrix;
                uniforms.spotLightMap.value = lights.state.spotLightMap;
                uniforms.pointShadowMap.value = lights.state.pointShadowMap;
                uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix;
                // TODO (abelnation): add area lights shadow info to uniforms

            }
            var progUniforms = program.getUniforms();
            var uniformsList = WebGLUniforms.seqWithValue(progUniforms.seq, uniforms);

            materialProperties.currentProgram = program;
            materialProperties.uniformsList = uniformsList;
            return program;
        }

        public void updateCommonMaterialProperties(Material material, WebGLPrograms.Parameters parameters)
        {

            var materialProperties = properties.get(material);

            materialProperties.outputEncoding = parameters.outputEncoding;
            materialProperties.instancing = parameters.instancing;
            materialProperties.skinning = parameters.skinning;
            materialProperties.morphTargets = parameters.morphTargets;
            materialProperties.morphNormals = parameters.morphNormals;
            materialProperties.morphColors = parameters.morphColors;
            materialProperties.morphTargetsCount = parameters.morphTargetsCount;
            materialProperties.numClippingPlanes = parameters.numClippingPlanes;
            materialProperties.numIntersection = parameters.numClipIntersection;
            materialProperties.vertexAlphas = parameters.vertexAlphas;
            materialProperties.vertexTangents = parameters.vertexTangents;
            materialProperties.toneMapping = parameters.toneMapping;

        }

        public WebGLProgram setProgram(Camera camera, Object3D scene, BufferGeometry geometry, Material material, Object3D _object)
        {

            if (scene is Scene != true) scene = _emptyScene; // scene could be a Mesh, Line, Points, ...

            textures.resetTextureUnits();
            var sceneObj = scene as Scene;
            var fog = sceneObj.fog;
            var environment = material is MeshStandardMaterial ? sceneObj.environment : null;
            var encoding = (_currentRenderTarget == null) ? _this.outputEncoding : (_currentRenderTarget.isXRRenderTarget ? _currentRenderTarget.texture.encoding : LinearEncoding);
            var envMap = (material is MeshStandardMaterial ? cubeuvmaps : cubemaps).get(material.envMap ?? environment);
            var vertexAlphas = material.vertexColors && geometry.attributes.color != null && geometry.attributes.color.itemSize == 4;
            var vertexTangents = material.normalMap != null && geometry.attributes["tangent"] != null;
            var morphTargets = geometry.morphAttributes.position != null;
            var morphNormals = geometry.morphAttributes.normal != null;
            var morphColors = geometry.morphAttributes.color != null;
            var toneMapping = material.toneMapped ? _this.toneMapping : NoToneMapping;

            var morphAttribute = geometry.morphAttributes.position ?? geometry.morphAttributes.normal ?? geometry.morphAttributes.color;
            var morphTargetsCount = (morphAttribute != null) ? morphAttribute.Length : 0;

            var materialProperties = properties.get(material);
            var lights = currentRenderState.state.lights;

            if (_clippingEnabled)
            {

                if (_localClippingEnabled || camera != _currentCamera)
                {

                    var useCache =
                        camera == _currentCamera &&
                        material.id == _currentMaterialId;

                    // we might want to call this function with some ClippingGroup
                    // object instead of the material, once it becomes feasible
                    // (#8465, #8379)
                    clipping.setState(material, camera, useCache);

                }

            }

            //

            var needsProgramChange = false;

            if (material.version == materialProperties.__version)
            {

                if (materialProperties.needsLights && materialProperties.lightsStateVersion != lights.state.version)
                {

                    needsProgramChange = true;

                }
                else if (materialProperties.outputEncoding != encoding)
                {

                    needsProgramChange = true;

                }
                else if (_object is InstancedMesh && materialProperties.instancing == false)
                {

                    needsProgramChange = true;

                }
                else if (!(_object is InstancedMesh) && materialProperties.instancing)
                {

                    needsProgramChange = true;

                }
                else if (_object is SkinnedMesh && materialProperties.skinning == false)
                {

                    needsProgramChange = true;

                }
                else if (!(_object is SkinnedMesh) && materialProperties.skinning)
                {

                    needsProgramChange = true;

                }
                else if (materialProperties.envMap != envMap)
                {

                    needsProgramChange = true;

                }
                else if (material.fog && materialProperties.fog != fog)
                {

                    needsProgramChange = true;

                }
                else if (materialProperties.numClippingPlanes > -1 &&
                    (materialProperties.numClippingPlanes != clipping.numPlanes ||
                    materialProperties.numIntersection != clipping.numIntersection))
                {

                    needsProgramChange = true;

                }
                else if (materialProperties.vertexAlphas != vertexAlphas)
                {

                    needsProgramChange = true;

                }
                else if (materialProperties.vertexTangents != vertexTangents)
                {

                    needsProgramChange = true;

                }
                else if (materialProperties.morphTargets != morphTargets)
                {

                    needsProgramChange = true;

                }
                else if (materialProperties.morphNormals != morphNormals)
                {

                    needsProgramChange = true;

                }
                else if (materialProperties.morphColors != morphColors)
                {

                    needsProgramChange = true;

                }
                else if (materialProperties.toneMapping != toneMapping)
                {

                    needsProgramChange = true;

                }
                else if (capabilities.isWebGL2 && materialProperties.morphTargetsCount != morphTargetsCount)
                {

                    needsProgramChange = true;

                }

            }
            else
            {

                needsProgramChange = true;
                materialProperties.__version = material.version;

            }

            //

            var program = materialProperties.currentProgram;// as WebGLProgram;

            if (needsProgramChange)
            {

                program = getProgram(material, scene, _object);

            }

            var refreshProgram = false;
            var refreshMaterial = false;
            var refreshLights = false;

            WebGLUniforms p_uniforms = program.getUniforms();
            var m_uniforms = materialProperties.uniforms;

            if (state.useProgram(program.program))
            {

                refreshProgram = true;
                refreshMaterial = true;
                refreshLights = true;

            }

            if (material.id != _currentMaterialId)
            {

                _currentMaterialId = material.id;

                refreshMaterial = true;

            }

            if (refreshProgram || _currentCamera != camera)
            {

                p_uniforms.setValue("projectionMatrix", camera.projectionMatrix, null);

                if (capabilities.logarithmicDepthBuffer)
                {

                    p_uniforms.setValue("logDepthBufFC",
                        2.0 / (Math.Log((camera as PerspectiveCamera).far + 1.0) / MathEx.LN2));

                }

                if (_currentCamera != camera)
                {

                    _currentCamera = camera;

                    // lighting uniforms depend on the camera so enforce an update
                    // now, in case this material supports lights - or later, when
                    // the next material that does gets activated:

                    refreshMaterial = true;     // set to true on material change
                    refreshLights = true;       // remains set until update done

                }

                // load material specific uniforms
                // (shader material also gets them for the sake of genericity)

                if (material is ShaderMaterial ||
                    material is MeshPhongMaterial ||
                    material is MeshToonMaterial ||
                    material is MeshStandardMaterial ||
                    material.envMap != null)
                {

                    var uCamPos = p_uniforms.map["cameraPosition"];

                    if (uCamPos != null)
                    {

                        uCamPos.setValue(
                            _vector3.SetFromMatrixPosition(camera.matrixWorld), null);

                    }

                }

                if (material is MeshPhongMaterial ||
                    material is MeshToonMaterial ||
                    material is MeshLambertMaterial ||
                    material is MeshBasicMaterial ||
                    material is MeshStandardMaterial ||
                    material is ShaderMaterial)
                {

                    p_uniforms.setValue("isOrthographic", camera is OrthographicCamera, null);

                }

                if (material is MeshPhongMaterial ||
                    material is MeshToonMaterial ||
                    material is MeshLambertMaterial ||
                    material is MeshBasicMaterial ||
                    material is MeshStandardMaterial ||
                    material is ShaderMaterial ||
                    material is ShadowMaterial ||
                    _object is SkinnedMesh)
                {

                    p_uniforms.setValue("viewMatrix", camera.matrixWorldInverse);

                }

            }

            // skinning and morph target uniforms must be set even if material didn"t change
            // auto-setting of texture unit for bone and morph texture must go before other textures
            // otherwise textures used for skinning and morphing can take over texture units reserved for other material textures

            if (_object is SkinnedMesh)
            {

                p_uniforms.setOptional(_object, "bindMatrix");
                p_uniforms.setOptional(_object, "bindMatrixInverse");

                var skeleton = (_object as SkinnedMesh).skeleton;

                if (skeleton != null)
                {

                    if (capabilities.floatVertexTextures)
                    {

                        if (skeleton.boneTexture == null) skeleton.computeBoneTexture();

                        p_uniforms.setValue("boneTexture", skeleton.boneTexture, textures);
                        p_uniforms.setValue("boneTextureSize", skeleton.boneTextureSize);

                    }
                    else
                    {

                        console.warn("THREE.WebGLRenderer: SkinnedMesh can only be used with WebGL 2. With WebGL 1 OES_texture_float and vertex textures support is required.");

                    }

                }

            }

            var morphAttributes = geometry.morphAttributes;

            if (morphAttributes.position != null || morphAttributes.normal != null || (morphAttributes.color != null && capabilities.isWebGL2))
            {

                morphtargets.update(_object as IMorphTargets, geometry, program);

            }

            if (refreshMaterial || materialProperties.receiveShadow != _object.receiveShadow)
            {

                materialProperties.receiveShadow = _object.receiveShadow;
                p_uniforms.setValue("receiveShadow", _object.receiveShadow);

            }

            // https://github.com/mrdoob/three.js/pull/24467#issuecomment-1209031512

            if (material.type == "MeshGouraudMaterial" && material.envMap != null)
            {

                m_uniforms.envMap.value = envMap;

                m_uniforms.flipEnvMap.value = (envMap is CubeTexture && envMap.isRenderTargetTexture == false) ? -1 : 1;

            }

            if (refreshMaterial)
            {

                p_uniforms.setValue("toneMappingExposure", _this.toneMappingExposure);

                if (materialProperties.needsLights)
                {

                    // the current material requires lighting info

                    // note: all lighting uniforms are always set correctly
                    // they simply reference the renderer"s state for their
                    // values
                    //
                    // use the current material"s .needsUpdate flags to set
                    // the GL state when required

                    markUniformsLightsNeedsUpdate(m_uniforms, refreshLights);

                }

                // refresh uniforms common to several materials

                if (fog != null && material.fog)
                {

                    materials.refreshFogUniforms(m_uniforms, fog);

                }

                materials.refreshMaterialUniforms(m_uniforms, material, _pixelRatio, _height, _transmissionRenderTarget);

                WebGLUniforms.upload(materialProperties.uniformsList, m_uniforms, textures);

            }

            if (material is ShaderMaterial && (material as ShaderMaterial).uniformsNeedUpdate)
            {

                WebGLUniforms.upload(materialProperties.uniformsList, m_uniforms, textures);
                (material as ShaderMaterial).uniformsNeedUpdate = false;

            }

            if (material is SpriteMaterial)
            {

                p_uniforms.setValue("center", (_object as Sprite).center);

            }

            // common matrices

            p_uniforms.setValue("modelViewMatrix", _object.modelViewMatrix);
            p_uniforms.setValue("normalMatrix", _object.normalMatrix);
            p_uniforms.setValue("modelMatrix", _object.matrixWorld);

            // UBOs
            //by yf albubo ubos放到基类，看各个类自己支持
            if (material.uniformsGroups != null)// ||material is ShaderMaterial || material is RawShaderMaterial)
            {

                var groups = material.uniformsGroups;

                for (int i = 0, l = groups.Length; i < l; i++)
                {

                    if (capabilities.isWebGL2)
                    {

                        var group = groups[i];

                        uniformsGroups.update(group, program);
                        uniformsGroups.bind(group, program);

                    }
                    else
                    {

                        console.warn("THREE.WebGLRenderer: Uniform Buffer Objects can only be used with WebGL 2.");

                    }

                }

            }

            return program;

        }
        // If uniforms are marked as clean, they don"t need to be loaded to the GPU.

        public void markUniformsLightsNeedsUpdate(Uniforms uniforms, bool value)
        {

            uniforms.ambientLightColor.needsUpdate = value;
            uniforms.lightProbe.needsUpdate = value;

            uniforms.directionalLights.needsUpdate = value;
            uniforms.directionalLightShadows.needsUpdate = value;
            uniforms.pointLights.needsUpdate = value;
            uniforms.pointLightShadows.needsUpdate = value;
            uniforms.spotLights.needsUpdate = value;
            uniforms.spotLightShadows.needsUpdate = value;
            uniforms.rectAreaLights.needsUpdate = value;
            uniforms.hemisphereLights.needsUpdate = value;

        }

        public bool materialNeedsLights(Material material)
        {

            return material is MeshLambertMaterial || material is MeshToonMaterial || material is MeshPhongMaterial ||
                material is MeshStandardMaterial || material is ShadowMaterial ||
                (material is ShaderMaterial && (material as ShaderMaterial).lights);

        }

        public int getActiveCubeFace()
        {

            return _currentActiveCubeFace;

        }

        public int getActiveMipmapLevel()
        {

            return _currentActiveMipmapLevel;

        }

        public WebGLRenderTarget getRenderTarget()
        {
            return _currentRenderTarget;
        }

        public void setRenderTargetTextures(WebGLRenderTarget renderTarget, int colorTexture, int depthTexture)
        {

            properties.get(renderTarget.texture)["__webglTexture"] = colorTexture;
            properties.get(renderTarget.depthTexture)["__webglTexture"] = depthTexture;

            var renderTargetProperties = properties.get(renderTarget);
            renderTargetProperties["__hasExternalTextures"] = true;

            if ((bool)renderTargetProperties["__hasExternalTextures"])
            {

                renderTargetProperties["__autoAllocateDepthBuffer"] = depthTexture == 0;

                if (!(bool)renderTargetProperties["__autoAllocateDepthBuffer"])
                {

                    // The multisample_render_to_texture extension doesn"t work properly if there
                    // are midframe flushes and an external depth buffer. Disable use of the extension.
                    if (extensions.has("WEBGL_multisampled_render_to_texture"))
                    {

                        console.warn("THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided");
                        renderTargetProperties["__useRenderToTexture"] = false;

                    }

                }

            }

        }

        public void setRenderTargetFramebuffer(WebGLRenderTarget renderTarget, int? defaultFramebuffer)
        {
            var renderTargetProperties = properties.get(renderTarget);
            renderTargetProperties["__webglFramebuffer"] = defaultFramebuffer;
            renderTargetProperties["__useDefaultFramebuffer"] = defaultFramebuffer == null;

        }

        public void setRenderTarget(WebGLRenderTarget renderTarget, int activeCubeFace = 0, int activeMipmapLevel = 0)
        {

            _currentRenderTarget = renderTarget;
            _currentActiveCubeFace = activeCubeFace;
            _currentActiveMipmapLevel = activeMipmapLevel;

            var useDefaultFramebuffer = true;
            GLInt framebuffer = null;
            var isCube = false;
            var isRenderTarget3D = false;

            if (renderTarget != null)
            {

                var renderTargetProperties = properties.get(renderTarget);

                if (renderTargetProperties["__useDefaultFramebuffer"] != null)
                {

                    // We need to make sure to rebind the framebuffer.
                    state.bindFramebuffer(gl.FRAMEBUFFER, null);
                    useDefaultFramebuffer = false;
                }
                else if (renderTargetProperties["__webglFramebuffer"] == null)
                {

                    textures.setupRenderTarget(renderTarget);

                }
                else if ((bool?)renderTargetProperties["__hasExternalTextures"] ?? false)
                {

                    // Color and depth texture must be rebound in order for the swapchain to update.
                    textures.rebindTextures(renderTarget, (properties.get(renderTarget.texture)["__webglTexture"] as GLInt).Value, (properties.get(renderTarget.depthTexture)["__webglTexture"] as GLInt).Value);

                }

                var texture = renderTarget.texture;

                if (texture is Data3DTexture || texture is DataArrayTexture || texture is CompressedArrayTexture)
                {

                    isRenderTarget3D = true;

                }

                var __webglFramebuffer = properties.get(renderTarget)["__webglFramebuffer"];

                if (renderTarget is WebGLCubeRenderTarget)
                {

                    framebuffer = (GLInt)(__webglFramebuffer as IList)[activeCubeFace];
                    isCube = true;

                }
                else if ((capabilities.isWebGL2 && renderTarget.samples > 0) && textures.useMultisampledRTT(renderTarget) == false)
                {

                    framebuffer = (GLInt)properties.get(renderTarget)["__webglMultisampledFramebuffer"];

                }
                else
                {

                    framebuffer = (GLInt)__webglFramebuffer;

                }

                _currentViewport.Copy(renderTarget.viewport);
                _currentScissor.Copy(renderTarget.scissor);
                _currentScissorTest = renderTarget.scissorTest;

            }
            else
            {

                _currentViewport.Copy(_viewport).MultiplyScalar(_pixelRatio).Floor();
                _currentScissor.Copy(_scissor).MultiplyScalar(_pixelRatio).Floor();
                _currentScissorTest = _scissorTest;

            }

            var framebufferBound = state.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);

            if (framebufferBound && capabilities.drawBuffers && useDefaultFramebuffer)
            {

                state.drawBuffers(renderTarget, framebuffer);

            }

            state.viewport(_currentViewport);
            state.scissor(_currentScissor);
            state.setScissorTest(_currentScissorTest);

            if (isCube)
            {

                var textureProperties = properties.get(renderTarget.texture);
                gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + activeCubeFace, (textureProperties["__webglTexture"] as GLInt).Value, activeMipmapLevel);

            }
            else if (isRenderTarget3D)
            {

                var textureProperties = properties.get(renderTarget.texture);
                var layer = activeCubeFace >= 0 ? activeCubeFace : 0;
                gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, (textureProperties["__webglTexture"] as GLInt)?.Value ?? 0, activeMipmapLevel >= 0 ? activeMipmapLevel : 0, layer);

            }

            _currentMaterialId = -1; // reset current material to ensure correct uniform bindings

        }

        public void readRenderTargetPixels(WebGLRenderTarget renderTarget, int x, int y, int width, int height, byte[] buffer, int activeCubeFaceIndex = 0)
        {

            if (!(renderTarget != null && renderTarget is WebGLRenderTarget))
            {

                console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");
                return;

            }

            var framebuffer = properties.get(renderTarget)["__webglFramebuffer"];

            if (renderTarget is WebGLCubeRenderTarget && activeCubeFaceIndex > 0)
            {

                framebuffer = (framebuffer as IList)[activeCubeFaceIndex];

            }

            if (framebuffer != null)
            {

                state.bindFramebuffer(gl.FRAMEBUFFER, framebuffer as GLInt);

                try
                {

                    var texture = renderTarget.texture;
                    var textureFormat = texture.format;
                    var textureType = texture.type;

                    if (textureFormat != RGBAFormat && utils.convert(textureFormat) != (int)gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT))
                    {

                        console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.");
                        return;

                    }

                    var halfFloatSupportedByExt = (textureType == HalfFloatType);//&& (extensions.has("EXT_color_buffer_half_float") || (capabilities.isWebGL2 && extensions.has("EXT_color_buffer_float")));

                    if (textureType != UnsignedByteType && utils.convert(textureType) != (int)gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE) && // Edge and Chrome Mac < 52 (#9513)
                        !(textureType == FloatType && (capabilities.isWebGL2 || extensions.has("OES_texture_float") || extensions.has("WEBGL_color_buffer_float"))) && // Chrome Mac >= 52 and Firefox
                        !halfFloatSupportedByExt)
                    {

                        console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.");
                        return;

                    }

                    // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604)

                    if ((x >= 0 && x <= (renderTarget.width - width)) && (y >= 0 && y <= (renderTarget.height - height)))
                    {

                        gl.readPixels(x, y, width, height, utils.convert(textureFormat), utils.convert(textureType), buffer);

                    }

                }
                finally
                {

                    // restore framebuffer of current render target if necessary

                    var _framebuffer = (_currentRenderTarget != null) ? properties.get(_currentRenderTarget)["__webglFramebuffer"] : null;
                    state.bindFramebuffer(gl.FRAMEBUFFER, (GLInt)_framebuffer);

                }
            }

        }

        public void copyFramebufferToTexture(Vector2 position, Texture texture, int level = 0)
        {

            var levelScale = Math.Pow(2, -level);
            var width = Math.Floor(texture.image.width * levelScale);
            var height = Math.Floor(texture.image.height * levelScale);

            textures.setTexture2D(texture, 0);

            gl.copyTexSubImage2D(gl.TEXTURE_2D, level, 0, 0, (int)position.X, (int)position.Y, (int)width, (int)height);

            state.unbindTexture();

        }

        public void copyTextureToTexture(Vector3 position, Texture srcTexture, Texture dstTexture, int level = 0)
        {

            var width = srcTexture.image.width;
            var height = srcTexture.image.height;
            var glFormat = utils.convert(dstTexture.format);
            var glType = utils.convert(dstTexture.type);

            textures.setTexture2D(dstTexture, 0);

            // As another texture upload may have changed pixelStorei
            // parameters, make sure they are correct for the dstTexture
            // 这边没找到opengl Flip_Y
            //gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY);
            //gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha);
            gl.pixelStorei(gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment);
            if (srcTexture is DataTexture)
            {
                var img = (srcTexture as DataTexture).image as Image;
                var ptr = img.startRead();
                gl.texSubImage2D(gl.TEXTURE_2D, level, (int)position.X, (int)position.Y, width, height, glFormat, glType, ptr);
                img.endRead();
            }
            else
            {
                if (srcTexture is CompressedTexture)
                {
                    gl.compressedTexSubImage2D(gl.TEXTURE_2D, level, (int)position.X, (int)position.Y, srcTexture.mipmaps[0].width, srcTexture.mipmaps[0].height, glFormat, srcTexture.mipmaps[0].data.byteSize(), srcTexture.mipmaps[0].startRead());
                    srcTexture.mipmaps[0].endRead();
                }
                else
                {
                    var image = srcTexture.image as Image;
                    gl.texSubImage2D(gl.TEXTURE_2D, level, (int)position.X, (int)position.Y, srcTexture.image.width - (int)position.X, srcTexture.image.height - (int)position.Y, glFormat, glType, image.startRead());
                    image.endRead();
                }

            }

            // Generate mipmaps only when copying level 0
            if (level == 0 && dstTexture.generateMipmaps) gl.generateMipmap(gl.TEXTURE_2D);

            state.unbindTexture();

        }

        public void copyTextureToTexture3D(Box3 sourceBox, Vector3 position, Texture srcTexture, Texture dstTexture, int level = 0)
        {


            var width = (int)(sourceBox.Max.X - sourceBox.Min.X + 1);
            var height = (int)(sourceBox.Max.Y - sourceBox.Min.Y + 1);
            var depth = (int)(sourceBox.Max.Z - sourceBox.Min.Z + 1);
            var glFormat = utils.convert(dstTexture.format);
            var glType = utils.convert(dstTexture.type);
            int glTarget;

            if (dstTexture is Data3DTexture)
            {

                textures.setTexture3D(dstTexture, 0);
                glTarget = gl.TEXTURE_3D;

            }
            else if (dstTexture is DataArrayTexture)
            {

                textures.setTexture2DArray(dstTexture, 0);
                glTarget = gl.TEXTURE_2D_ARRAY;

            }
            else
            {

                console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.");
                return;

            }

            //gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY);
            //gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha);
            gl.pixelStorei(gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment);

            var unpackRowLen = (int)gl.getParameter(gl.UNPACK_ROW_LENGTH);
            var unpackImageHeight = (int)gl.getParameter(gl.UNPACK_IMAGE_HEIGHT);
            var unpackSkipPixels = (int)gl.getParameter(gl.UNPACK_SKIP_PIXELS);
            var unpackSkipRows = (int)gl.getParameter(gl.UNPACK_SKIP_ROWS);
            var unpackSkipImages = (int)gl.getParameter(gl.UNPACK_SKIP_IMAGES);

            var image = (srcTexture is CompressedTexture ? srcTexture.mipmaps[0] : srcTexture.image) as Image;

            gl.pixelStorei(gl.UNPACK_ROW_LENGTH, image.width);
            gl.pixelStorei(gl.UNPACK_IMAGE_HEIGHT, image.height);
            gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, (int)sourceBox.Min.X);
            gl.pixelStorei(gl.UNPACK_SKIP_ROWS, (int)sourceBox.Min.Y);
            gl.pixelStorei(gl.UNPACK_SKIP_IMAGES, (int)sourceBox.Min.Z);
            if (srcTexture is DataTexture || srcTexture is Data3DTexture)
            {
                gl.texSubImage3D(glTarget, level, (int)position.X, (int)position.Y, (int)position.Z, width, height, depth, glFormat, glType, image.startRead());
                image.endRead();

            }
            else
            {

                if (srcTexture is CompressedArrayTexture)
                {
                    console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture.");
                    gl.compressedTexSubImage3D(glTarget, level, (int)position.X, (int)position.Y, (int)position.Z, width, height, depth, glFormat, image.data.byteSize(), image.startRead());
                    image.endRead();
                }
                else
                {
                    gl.texSubImage3D(glTarget, level, (int)position.X, (int)position.Y, (int)position.Z, width, height, depth, glFormat, glType, image.startRead());
                    image.endRead();
                }

            }

            gl.pixelStorei(gl.UNPACK_ROW_LENGTH, unpackRowLen);
            gl.pixelStorei(gl.UNPACK_IMAGE_HEIGHT, unpackImageHeight);
            gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, unpackSkipPixels);
            gl.pixelStorei(gl.UNPACK_SKIP_ROWS, unpackSkipRows);
            gl.pixelStorei(gl.UNPACK_SKIP_IMAGES, unpackSkipImages);

            // Generate mipmaps only when copying level 0
            if (level == 0 && dstTexture.generateMipmaps) gl.generateMipmap(glTarget);
            state.unbindTexture();
        }

        public void initTexture(Texture texture)
        {

            if (texture is CubeTexture)
            {

                textures.setTextureCube(texture, 0);

            }
            else if (texture is Data3DTexture)
            {

                textures.setTexture3D(texture, 0);

            }
            else if (texture is DataArrayTexture || texture is CompressedArrayTexture)
            {

                textures.setTexture2DArray(texture, 0);

            }
            else
            {

                textures.setTexture2D(texture, 0);

            }

            state.unbindTexture();

        }

        public void resetState()
        {
            _currentActiveCubeFace = 0;
            _currentActiveMipmapLevel = 0;
            _currentRenderTarget = null;

            state.reset();
            bindingStates.reset();
        }
        public bool physicallyCorrectLights
        {
            get
            { // @deprecated, r150

                console.warn("THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead.");
                return !this.useLegacyLights;
            }
            set
            {
                // @deprecated, r150
                console.warn("THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead.");
                this.useLegacyLights = !value;
            }
        }
    }
}
